<?php
/**
 * Definition of an XML tag.
 *
 * @author  Stephan Schmidt <stephan.schmidt@schlund.de>
 * @author  Frank Kleine <frank.kleine@schlund.de>
 */
XJConfLoader::load('converters.factories.ValueConverterFactoryChain',
                   'definitions.AttributeDefinition',
                   'definitions.CDataDefinition',
                   'definitions.ConstructorDefinition',
                   'definitions.FactoryMethodDefinition',
                   'definitions.Definition',
                   'exceptions.ValueConversionException',
                   'exceptions.XJConfException'
);
/**
 * Definition of an XML tag.
 *
 * @package     XJConf
 * @subpackage  definitions
 */
class TagDefinition implements Definition
{
    /**
     * the name
     *
     * @var  string
     */
	private $name           = null;
	/**
     * the name of the tag
     *
     * @var  string
     */
	private $tagName        = null;
	/**
     * type of the tag
     *
     * @var  string
     */
	private $type           = null;
	/**
     * list of attribute definitions
     *
     * @var  array<AttributeDefinition>
     */
	private $atts           = array();
	/**
     * name of the setter
     *
     * @var  string
     */
	private $setter         = null;
	/**
	 * name of attribute that contains the name
	 *
	 * @var  string
	 */
	private $nameAttribute  = null;
	/**
	 * definition of how to construct the object
	 *
	 * @var  ConstructorDefinition
	 */
    private $constructor    = null;
    /**
     * definition of factory that is able to construct the object
     *
     * @var  FactoryMethodDefinition
     */
    private $factoryMethod  = null;
    /**
     * converts the value
     *
     * @var  ValueConverter
     */
    private $valueConverter;
    /**
     * definition of tag content
     *
     * @var   CDataDefinition
     * @todo  Eventually call the setter method for the cdata
     */
	private $cdata          = null;

    /**
     * Create a new tag definition
     *
     * @param   string  $name  name of the tag
     * @param   string  $type  type of the tag
     * @throws  XJConfException
     */
	public function __construct($name, $type)
	{
		if (null == $name || strlen($name) == 0) {
			throw new XJConfException('TagDefinition needs a name.');
		}
		if (null == $type || strlen($type) == 0) {
			throw new XJConfException('TagDefinition needs a type.');
		}

		$this->name    = $name;
		$this->tagName = $name;
		$this->setType($type);
	}

	/**
	 * set the name of the value
	 *
	 * @param  string  $name
	 */
	public function setName($name)
	{
		$this->name = $name;
	}

	/**
	 * get the name of the value
	 *
	 * @return  string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Set the type of the tag
	 *
	 * @param  string  $type
	 */
	public function setType($type)
	{
		$this->type = $type;
	}

	/**
	 * get the type of the tag
	 *
	 * @return  string
	 */
	public function getType()
	{
		return $this->type;
	}

	/**
	 * Convert the value of the tag.
	 *
	 * @param   Tag    $tag  tag that will be converted
	 * @return  mixed  converted value
	 * @throws  ValueConversionException
	 */
	public function convertValue(Tag $tag)
    {
		// get the data
		$data = $tag->getContent();
		if (null == $data) {
			$data = '';
		}

        // no constructor definition has been set,
        // create a new one
        if (null == $this->constructor && null == $this->factoryMethod) {
            $this->constructor = new ConstructorDefinition();
            $this->constructor->addChildDefinition(new CDataDefinition());
        }

        $instance = $this->getValueConverter()->convertValue($tag, $this);
		return $instance;
	}

	/**
     * Get the type of the tag
     *
     * @return  string
     */
    public function getValueType(Tag $tag)
    {
        return $this->getValueConverter()->getType();
    }

    /**
	 * Set the setter method
	 *
	 * @param  string  $setter  name of the setter method
	 */
	public function setSetterMethod($setter)
	{
		$this->setter = $setter;
	}

	/**
	 * Get the name of the setter method that should be used
	 *
	 * @return  string
	 */
	public function getSetterMethod(Tag $tag)
	{
		if (null != $this->setter) {
			return $this->setter;
		}

		// no name, the parent should be a collection
		if ('__none' == $this->name) {
			return null;
		}

		return 'set' . ucfirst($this->getKey($tag));
	}

    /**
     * Add a new child definition
     *
     * Possible definitions are:
     * - AttributeDefinition
     * - ConstructorDefinition
     * - FactoryMethodDefinition
     * - CDataDefinition
     *
     * @param  Definition  $def
     */
    public function addChildDefinition(Definition $def)
    {
        if ($def instanceof AttributeDefinition) {
            $this->addAttribute($def);
            return;
        }

        if ($def instanceof FactoryMethodDefinition) {
            $this->factoryMethod = $def;
            return;
        }

        if ($def instanceof ConstructorDefinition) {
            $this->constructor = $def;
            return;
        }

        if ($def instanceof CDataDefinition) {
            $this->cdata = $def;
            return;
        }
    }

    /**
     * Checks whether this definition has a specific child condition
     *
     * @param   string   $def
     * @return  boolean  true if definition has a specific child condition, else false
     */
    public function hasChildDefinition($def)
    {
        $children = $this->getChildDefinitions();
        foreach ($children as $child) {
            if (get_class($child) == $def) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns the first found definition of type $def
     *
     * @param   string   $def
     * @return  Definition
     */
    public function getChildDefinition($def)
    {
        $children = $this->getChildDefinitions();
        foreach ($children as $child) {
            if (get_class($child) == $def) {
                return $child;
            }
        }

        return null;
    }

    /**
     * Return all child definitions.
     *
     * @return  array
     */
    public function getChildDefinitions()
    {
        $children = $this->atts;
        if ($this->factoryMethod instanceof Definition) {
            $children[] = $this->factoryMethod;
        }

        if ($this->constructor instanceof Definition) {
            $children[] = $this->constructor;
        }

        if ($this->cdata instanceof Definition) {
            $children[] = $this->cdata;
        }

        return $children;
    }

	/**
	 * Add an attribute to the tag
	 *
	 * @param  AttributeDefinition  $att
	 */
	public function addAttribute(AttributeDefinition $att)
	{
		array_push($this->atts, $att);
	}

	/**
	 * Return list of attributes for this tag
	 *
	 * @return  array
	 */
	public function getAttributes() {
	    return $this->atts;
	}

	/**
	 * Set the name of the tag
	 *
	 * @param name
	 */
	public function setTagName($name)
	{
		$this->tagName = $name;
	}

	/**
	 * Set the attribute that will be used as key.
     *
	 * @return   name of the value
	 */
	public function setKeyAttribute($att)
	{
        $this->name          = '__attribute';
		$this->nameAttribute = $att;
	}

	/**
	 * get the name of the tag
	 *
	 * @return  string
	 */
	public function getTagName()
	{
		return $this->tagName;
	}

	/**
	 * get the name of the tag
	 *
	 * @return  string
	 */
	public function getKey(DefinedTag $tag)
	{
		if ('__attribute' == $this->name) {
        	return $tag->getAttribute($this->nameAttribute);
        }

        return $this->name;
	}

    /**
     * Check, whether the value supports indexed children
     *
     * @return	boolean
     */
	public function supportsIndexedChildren()
	{
		if ($this->getType() == 'array') {
			return true;
		}

		return false;
	}

    /**
     * Get the value converter for this tag
     *
     * @return  ValueConverter
     */
    private function getValueConverter()
    {
        if (null == $this->valueConverter) {
            $this->valueConverter = ValueConverterFactoryChain::getFactory($this)->createValueConverter($this);
        }

        return $this->valueConverter;
    }
}
?>